#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#define __FAVOR_BSD
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <list>
#include <stdlib.h>


#include "../threads/thread_key.h"
#include "net.h"
#include <glog/logging.h>

using namespace std;

int in_cksum(void *data, int len)
{
    uint16_t *addr = (uint16_t*)data;
    int sum;
    union
    {   
        uint16_t s;
        uint8_t b[2];
    }pad;

    sum = 0;

    while (len > 1)
    {   
        sum += *addr++;
        len -= 2;
    }   
    if (len == 1)
    {   
        pad.b[0] = *(uint8_t *)addr;
        pad.b[1] = 0;
        sum += pad.s;
    }   

    return (sum);
}

/* used internally for checksum stuff */
#define CKSUM_CARRY(x) (x = (x >> 16) + (x & 0xffff), (~(x + (x >> 16)) & 0xffff))

int setblock(int sd)
{
    int fd_flags;

    fd_flags = fcntl(sd, F_GETFL, 0);
    fd_flags &= ~O_NONBLOCK;

    if (fcntl(sd, F_SETFL, fd_flags) == -1) 
    {
        return -1;
    }
    return 0;
}

int setnonblock(int fd)
{
    int flags;

    flags = fcntl(fd, F_GETFL);
    if (flags < 0)
        return flags;

    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0)
        return -1;

    return 0;
}

void maximize_rcvbuf(const int sfd) 
{
    socklen_t intsize = sizeof(int);
    int last_good = 0;
    int min, max, avg;
    int old_size;

    /* Start with the default size. */
    if (getsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &old_size, &intsize) != 0) 
    {
        LOG(WARNING) << "getsocket(SO_ECVBUF) failed (" << strerror(errno) << ")";
        return;
    }

    /* Binary-search for the real maximum. */
    min = old_size;
    max = MAX_RECVBUF_SIZE;

    while (min <= max) 
    {
        avg = ((unsigned int)(min + max)) / 2;
        if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void *)&avg, intsize) == 0) 
        {
            last_good = avg;
            min = avg + 1;
        } 
        else 
        {
            max = avg - 1;
        }
    }
}
/*
 * Sets a socket's send buffer size to the maximum allowed by the system.
 */
void maximize_sndbuf(const int sfd) 
{
    socklen_t intsize = sizeof(int);
    int last_good = 0;
    int min, max, avg;
    int old_size;

    /* Start with the default size. */
    if (getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &old_size, &intsize) != 0) {
        LOG(WARNING) << "getsocket(SO_SNDBUF) failed (" << strerror(errno) << ")";
        return;
    }

    /* Binary-search for the real maximum. */
    min = old_size;
    max = MAX_SENDBUF_SIZE;

    while (min <= max) {
        avg = ((unsigned int)(min + max)) / 2;
        if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void *)&avg, intsize) == 0) {
            last_good = avg;
            min = avg + 1;
        } else {
            max = avg - 1;
        }
    }
}



int create_socket(const char* ip, int port, int type)
{
    int sockfd = -1;
    int flags = 1;
    struct sockaddr_in servaddr;

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    if (ip != NULL) {
        inet_pton(AF_INET, ip, &servaddr.sin_addr);
    } else {
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }

    sockfd = socket(AF_INET, type, 0);
    if (sockfd == -1) {
        LOG(WARNING) << "socket() failed! " << strerror(errno);
        return -1;
    }

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
    maximize_sndbuf(sockfd);
    maximize_rcvbuf(sockfd);

    if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr_in)) == -1) {
        close(sockfd);
        LOG(WARNING) << "bind() failed! " << strerror(errno);
        return -1;
    }

	if (listen(sockfd, 1024) == -1) {
		close(sockfd);
		LOG(WARNING) << "listen() failed! " << strerror(errno);
		return -1;
	}

    return sockfd;
}

#if ( defined( __linux__ ) || defined( __gnu_linux__ ) )
int send_udp_packet(int sockfd, 
                    SA* ssa, socklen_t ssalen, 
                    SA* dsa, socklen_t dsalen, 
                    void* payload, size_t payload_s, 
                    int flags)
{
    int ret = -1;
    struct msghdr msg;
    struct iovec iovecs[3];
    struct udphdr udphdr;
    struct iphdr iphdr;
    uint32_t udplen = sizeof(udphdr) + payload_s;

    uint32_t iplen  = sizeof(iphdr) + udplen;
    uint32_t sum = 0;
    struct sockaddr_in* ssa_in = (struct sockaddr_in*)ssa;
    struct sockaddr_in* dsa_in = (struct sockaddr_in*)dsa;
    int iovec_idx = 0;

    memset(iovecs, 0, sizeof(iovecs));

    memset(&iphdr, 0, sizeof(iphdr));
    iphdr.version   = 4;                       /* version 4 */
    iphdr.ihl       = 5;                       /* 20 byte header */
    iphdr.tos       = 0;                       /* IP tos */
    iphdr.tot_len   = htons(iplen);            /* total length */
    iphdr.id        = 1;                       /* IP ID */
    iphdr.frag_off  = 0;                       /* fragmentation flags */
    iphdr.ttl       = 64;                      /* time to live */
    iphdr.protocol  = IPPROTO_UDP;             /* transport protocol */
    iphdr.check     = 0;                       /* checksum */
    iphdr.saddr     = ssa_in->sin_addr.s_addr; /* source ip */
    iphdr.daddr     = dsa_in->sin_addr.s_addr; /* destination ip */
    sum = in_cksum(&iphdr, iplen);
    iphdr.check  = CKSUM_CARRY(sum);

    iovecs[iovec_idx].iov_base = &iphdr; 
    iovecs[iovec_idx].iov_len = sizeof(iphdr); 
    iovec_idx++;

    memset(&udphdr, 0, sizeof(udphdr));
    udphdr.uh_sport = ssa_in->sin_port;  /* source port */
    udphdr.uh_dport = dsa_in->sin_port;  /* destination port */
    udphdr.uh_ulen  = htons(udplen);     /* total length of UDP packet*/
    udphdr.uh_sum   = 0;                 /* checksum */

    sum = in_cksum(&(iphdr.saddr), 8);
    sum += ntohs(IPPROTO_UDP + udplen);
    sum += in_cksum(&udphdr, 8);
    sum += in_cksum(payload, payload_s);
    udphdr.uh_sum = CKSUM_CARRY(sum);

    iovecs[iovec_idx].iov_base = &udphdr;
    iovecs[iovec_idx].iov_len = sizeof(udphdr);
    iovec_idx++;

    if ((payload && !payload_s) || (!payload && payload_s))
    { 
        LOG(WARNING) << "payload inconsistency";
        return -1;
    }

    if (payload && payload_s)
    {
        iovecs[iovec_idx].iov_base = payload;
        iovecs[iovec_idx].iov_len = payload_s;
        iovec_idx++;
    }

    memset(&msg, 0, sizeof(struct msghdr));
    msg.msg_name = (void*)dsa;
    msg.msg_namelen = dsalen;
    msg.msg_iov = iovecs;
    msg.msg_iovlen = iovec_idx;

    ret = sendmsg(sockfd, &msg, flags);

    return ret;
}
#endif

list<net_interface_t*> net_interface_init(int port, int if_type, const char* specified_ip_list)
{
    struct ifaddrs *ifaddr, *ifa;
    int idx = 0;
    list<net_interface_t*> ilist;

    if (getifaddrs(&ifaddr) != 0) {   
        LOG(WARNING) << "Unable to retrieve interfaces";
        return ilist;
    }   

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {   
        if (ifa->ifa_addr->sa_family == AF_INET) {
            int type = INTERFACE_TYPE_WAN;

            void* tmpaddr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char address[INET_ADDRSTRLEN + 1] = {0};
            inet_ntop(AF_INET, tmpaddr, address, INET_ADDRSTRLEN);

            if ((ifa->ifa_flags & IFF_UP) == 0) {
                LOG(INFO) << "debug interface: [" << ifa->ifa_name 
                    << "] ip: [" << address << "]";
                continue;
            }

            if ((ifa->ifa_flags & IFF_BROADCAST) == 0) {
                LOG(INFO) << "debug interface: [" << ifa->ifa_name 
                    << "] ip: [" << address << "]";
                if (strncmp(address, "127.0.0.1", 9) != 0) {
                    continue;
                }
            }

            if (strncmp(address, "127", 3) == 0) {
                type = INTERFACE_TYPE_LAN;
            } else if (strncmp(address, "10", 2) == 0) {
                type = INTERFACE_TYPE_LAN;
            } else if (strncmp(address, "192.168", 7) == 0) {
                type = INTERFACE_TYPE_LAN;
            }

            for (int i = 16; i <= 31; i++) {
                char tmp[7];
                sprintf(tmp, "172.%d", i);
                if (strncmp(address, tmp, strlen(tmp)) == 0) {
                    type = INTERFACE_TYPE_LAN;
                }
            }

            if (if_type != INTERFACE_TYPE_ALL && type != if_type) {
                continue;
            }

            if (specified_ip_list != NULL && strstr(specified_ip_list, address) == NULL) {
                continue;
            }

            LOG(INFO) << "interface: [" << ifa->ifa_name
                << "] ip: [" << address << "]";
            net_interface_t* interface = (net_interface_t*)calloc(1, sizeof(net_interface_t));
            memcpy(&interface->sockaddr, ifa->ifa_addr, sizeof(struct sockaddr_in));
            interface->port = port;
            interface->sockaddr.sin_port = htons(port);
            interface->sockaddr_len = sizeof(struct sockaddr_in);
            strncpy(interface->address, address, INET_ADDRSTRLEN);
            strncpy(interface->ifa_name, ifa->ifa_name, IFNAMSIZ);
            interface->type = (enum INTERFACE_TYPE)type;
            interface->index = idx++;

            ilist.push_back(interface);
        }
    }
    LOG(INFO) << "num_interfaces: " << (int)ilist.size();
    freeifaddrs(ifaddr);
    return ilist;
}
//static void net_interface_element_free(gpointer data)
//{
//}

void net_interface_destroy(list<net_interface_t*>& if_list)
{
    list<net_interface_t*>::iterator it = if_list.begin();
    list<net_interface_t*>::iterator end = if_list.end();
    while (it != end) {
        net_interface_t* interface = *it;
        if (interface == NULL){
            return;
		}

        free(interface);
        it++;
    }
}
